home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / flockWebDetective.js < prev    next >
Text File  |  2007-10-18  |  67KB  |  2,028 lines

  1. // vim: ts=2 sw=2 expandtab cindent
  2. //
  3. // BEGIN FLOCK GPL
  4. //
  5. // Copyright Flock Inc. 2005-2007
  6. // http://flock.com
  7. //
  8. // This file may be used under the terms of of the
  9. // GNU General Public License Version 2 or later (the "GPL"),
  10. // http://www.gnu.org/licenses/gpl.html
  11. //
  12. // Software distributed under the License is distributed on an "AS IS" basis,
  13. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. // for the specific language governing rights and limitations under the
  15. // License.
  16. //
  17. // END FLOCK GPL
  18. //
  19.  
  20. const Cc = Components.classes;
  21. const Ci = Components.interfaces;
  22. const Cr = Components.results;
  23.  
  24.  
  25. const ENABLE_DEBUG = false; // switch to turn off slow debug code for production
  26. function DEBUG(x) { if (ENABLE_DEBUG) debug("flockWebDetective: "+x+"\n"); }
  27.  
  28. const CLASS_ID = Components.ID("{61F83B70-6B52-11DB-BD13-0800200C9A66}");
  29. const CLASS_NAME = "Flock Web Detective";
  30. const CONTRACT_ID = "@flock.com/web-detective;1";
  31. const INTERFACES = [
  32.   Components.interfaces.nsISupports,
  33.   Components.interfaces.nsIClassInfo,
  34.   Components.interfaces.nsIObserver,
  35.   Components.interfaces.nsITimerCallback,
  36.   Ci.flockIWebDetective,
  37.   Ci.flockIMigratable
  38. ];
  39.  
  40. // from nspr's prio.h
  41. const PR_RDONLY      = 0x01;
  42. const PR_WRONLY      = 0x02;
  43. const PR_RDWR        = 0x04;
  44. const PR_CREATE_FILE = 0x08;
  45. const PR_APPEND      = 0x10;
  46. const PR_TRUNCATE    = 0x20;
  47. const PR_SYNC        = 0x40;
  48. const PR_EXCL        = 0x80;
  49.  
  50. const DEFAULT_UPDATE_SERVER = "https://extensions.flock.com/webdetective/falcon/";
  51. const DEFAULT_UPDATE_INTERVAL = 1; // This is in days
  52.  
  53.  
  54. // ===================================================
  55. // ========== BEGIN flockWebDetective class ==========
  56. // ===================================================
  57.  
  58. function flockWebDetective()
  59. {
  60.   // Associative array where keys are service names and values are XML Docs
  61.   this.mRules = [];
  62.  
  63.   // Associative array of version strings by service
  64.   this.mVersions = [];
  65.  
  66.   // Associative array of strings by service
  67.   this.mStrings = [];
  68.  
  69.   // Associative array of session cookies by service
  70.   this.mSessionCookies = [];
  71.  
  72.   // Associative array of detect files that we've loaded for a given service
  73.   this.mDetectFiles = [];
  74.  
  75.   this.cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
  76.                              .getService(Components.interfaces.nsICookieManager);
  77.   this.mEnabled = true;
  78.  
  79.   this.startUpdateService();
  80. }
  81.  
  82. // BEGIN nsISupports interface
  83. flockWebDetective.prototype.QueryInterface =
  84. function flockWebDetective_QueryInterface(aIID)
  85. {
  86.   var interfaces = INTERFACES;
  87.   for (var i in interfaces) {
  88.     if (aIID.equals(interfaces[i])) {
  89.       return this;
  90.     }
  91.   }
  92.   throw Components.results.NS_ERROR_NO_INTERFACE;
  93. }
  94. // END nsISupports interface
  95.  
  96.  
  97. // BEGIN nsIClassInfo interface
  98. flockWebDetective.prototype.contractID = CONTRACT_ID;
  99. flockWebDetective.prototype.classID = CLASS_ID;
  100. flockWebDetective.prototype.classDescription = CLASS_NAME;
  101. flockWebDetective.prototype.implementationLanguage = Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT;
  102. flockWebDetective.prototype.flags = Components.interfaces.nsIClassInfo.SINGLETON;
  103.  
  104. flockWebDetective.prototype.getInterfaces =
  105. function flockWebDetective_getInterfaces(aCount)
  106. {
  107.   var interfaces = INTERFACES;
  108.   aCount.value = interfaces.length;
  109.   return interfaces;
  110. }
  111.  
  112. flockWebDetective.prototype.getHelperForLanguage =
  113. function flockWebDetective_getHelperForLanguage(aLanguage)
  114. {
  115.   return null;
  116. }
  117. // END nsIClassInfo interface
  118.  
  119.  
  120. // BEGIN nsIObserver interface
  121. flockWebDetective.prototype.observe =
  122. function flockWebDetective_observe(aSubject, aTopic, aData)
  123. {
  124. }
  125. // END nsIObserver interface
  126.  
  127.  
  128. // BEGIN nsITimerCallback interface
  129. flockWebDetective.prototype.notify =
  130. function flockWebDetective_notify(timer)
  131. {
  132.   try {
  133.     var prefs = Cc['@mozilla.org/preferences-service;1']
  134.       .getService(Ci.nsIPrefBranch)
  135.     var doUpdate = prefs.getBoolPref('flock.service.webdetective.update');
  136.     if (!doUpdate)
  137.       return;
  138.   }
  139.   catch (e) { }
  140.  
  141.   this.checkForUpdates();
  142. }
  143. // END nsITimerCallback interface
  144.  
  145.  
  146. // BEGIN flockIWebDetective interface
  147. flockWebDetective.prototype.detect =
  148. function flockWebDetective_detect(aServiceName, aType, aDocument, aResults)
  149. {
  150.   DEBUG("{flockIWebDetective}.detect('"+aServiceName+"', '"+aType+"')");
  151.   return this.innerDetect(aServiceName, aType, aDocument, null, aResults);
  152. }
  153.  
  154. flockWebDetective.prototype.detectForm =
  155. function flockWebDetective_detectForm(aServiceName, aType, aForm, aResults)
  156. {
  157.   DEBUG("{flockIWebDetective}.detectForm('"+aServiceName+"', '"+aType+"')");
  158.   aForm.QueryInterface(Components.interfaces.nsIDOMHTMLFormElement);
  159.   return this.innerDetect(aServiceName, aType, aForm.ownerDocument, aForm, aResults);
  160. }
  161.  
  162. flockWebDetective.prototype.detectCookies =
  163. function flockWebDetective_detectCookies(aServiceName, aType, aResults)
  164. {
  165.   DEBUG("{flockIWebDetective}.detectCookies('"+aServiceName+"', '"+aType+"')");
  166.   return this.innerDetect(aServiceName, aType, null, null, aResults);
  167. }
  168.  
  169. flockWebDetective.prototype.detectNoDOM =
  170. function flockWebDetective_detectNoDOM(aServiceName, aType, aURL, aDocumentText, aResults)
  171. {
  172.   DEBUG("{flockIWebDetective}.detectNoDOM('"+aServiceName+"', '"+aType+"')");
  173.   //DEBUG(aDocumentText);
  174.   var doc = {
  175.     noDOM: true,
  176.     documentElement: {
  177.       innerHTML: aDocumentText
  178.     },
  179.     URL: aURL
  180.   };
  181.   return this.innerDetect(aServiceName, aType, doc, null, aResults);
  182. }
  183.  
  184. flockWebDetective.prototype.getSessionCookies =
  185. function flockWebDetective_getSessionCookies(aServiceName)
  186. {
  187.   if (!this.mSessionCookies[aServiceName]) {
  188.     this.loadSessionCookies(aServiceName);
  189.   }
  190.   if (!this.mSessionCookies[aServiceName]) return null;
  191.   var wd = this;
  192.   return {
  193.     arr: wd.mSessionCookies[aServiceName],
  194.     idx: 0,
  195.     QueryInterface: function (aIID) {
  196.       if ( !aIID.equals(Components.interfaces.nsISupports) &&
  197.            !aIID.equals(Components.interfaces.nsISimpleEnumerator) )
  198.       {
  199.         throw Components.results.NS_ERROR_NO_INTERFACE;
  200.       }
  201.       return this;
  202.     },
  203.     hasMoreElements: function () {
  204.       return (this.idx < this.arr.length);
  205.     },
  206.     getNext: function () {
  207.       var c = this.arr[this.idx++];
  208.       var cookie = {
  209.         QueryInterface: function (aIID) {
  210.           if ( !aIID.equals(Components.interfaces.nsISupports) &&
  211.                !aIID.equals(Components.interfaces.nsICookie) )
  212.           {
  213.             throw Components.results.NS_ERROR_NO_INTERFACE;
  214.           }
  215.           return this;
  216.         },
  217.         host: c.host,
  218.         name: c.name,
  219.         path: c.path
  220.       };
  221.       return cookie.QueryInterface(Components.interfaces.nsICookie);
  222.     }
  223.   };
  224. }
  225.  
  226. flockWebDetective.prototype.getString =
  227. function flockWebDetective_getString(aServiceName, aURLName, aDefault)
  228. {
  229.   DEBUG("{flockIWebDetective}.getString('"+aServiceName+"', '"+aURLName+"')");
  230.   if (!this.mEnabled) return aDefault;
  231.   if (!this.mRules[aServiceName]) {
  232.     throw "No rules loaded for service: "+aServiceName;
  233.   }
  234.   if (!this.mStrings[aServiceName]) {
  235.     this.loadStrings(aServiceName);
  236.   }
  237.   if (this.mStrings[aServiceName][aURLName]) {
  238.     return this.mStrings[aServiceName][aURLName];
  239.   }
  240.   return aDefault;
  241. }
  242.  
  243. flockWebDetective.prototype.loadDetectFile =
  244. function flockWebDetective_loadDetectFile(aDetectFile)
  245. {
  246.   DEBUG("{flockIWebDetective}.loadDetectFile()");
  247.  
  248.   var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  249.                 .getService(Components.interfaces.nsIPrefService)
  250.                 .getBranch(null);
  251.   if (prefs.getPrefType("flock.service.webdetective.enabled")) {
  252.     this.mEnabled = prefs.getBoolPref("flock.service.webdetective.enabled");
  253.   }
  254.   if (!this.mEnabled) return;
  255.  
  256.   aDetectFile.QueryInterface(Components.interfaces.nsILocalFile);
  257.  
  258.   if ((!aDetectFile.exists()) || (!aDetectFile.isReadable())) {
  259.     throw Components.results.NS_ERROR_UNEXPECTED;
  260.   }
  261.  
  262.   // Load and parse the XML
  263.   var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  264.                       .createInstance(Components.interfaces.nsIFileInputStream);
  265.   fis.init(aDetectFile, PR_RDONLY, 0, 0);
  266.   DEBUG("available bytes: "+fis.available());
  267.   var domParser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
  268.                             .createInstance(Components.interfaces.nsIDOMParser);
  269.   var xmlDoc = domParser.parseFromStream(fis, "UTF-8", fis.available(), "text/xml");
  270.   fis.close();
  271.  
  272.   var serviceEl = xmlDoc.documentElement;
  273.   if (serviceEl.tagName != "service") {
  274.     DEBUG("PARSE ERROR: no 'service' element found");
  275.     throw Components.results.NS_ERROR_UNEXPECTED;
  276.   }
  277.   var serviceName = serviceEl.getAttribute("name");
  278.   if (!serviceName || (serviceName == "")) {
  279.     DEBUG("PARSE ERROR: 'service' element has no name");
  280.     throw Components.results.NS_ERROR_UNEXPECTED;
  281.   }
  282.   this.mRules[serviceName] = xmlDoc;
  283.   this.mDetectFiles[serviceName] = aDetectFile;
  284.   if (serviceEl.hasAttribute("version")) {
  285.     this.mVersions[serviceName] = serviceEl.getAttribute("version");
  286.   }
  287.   DEBUG("loaded detection rules for service: "+serviceName);
  288.   // Force the strings and cookies to be reloaded, since they may have changed
  289.   this.mStrings[serviceName] = null;
  290.   this.mSessionCookies[serviceName] = null;
  291. }
  292.  
  293. flockWebDetective.prototype.listServices =
  294. function flockWebDetective_listServices()
  295. {
  296.   var svcEnum = {
  297.     _arr: [],
  298.     QueryInterface: function (aIID) {
  299.       if (aIID.equals(Ci.nsISupports)) return this;
  300.       if (aIID.equals(Ci.nsISimpleEnumerator)) return this;
  301.       throw Cr.NS_ERROR_NO_INTERFACE;
  302.     },
  303.     hasMoreElements: function () {
  304.       return (this._arr.length > 0);
  305.     },
  306.     getNext: function () {
  307.       return {
  308.         QueryInterface: function (aIID) {
  309.           if (aIID.equals(Ci.nsISupports)) return this;
  310.           if (aIID.equals(Ci.nsISupportsPrimitive)) return this;
  311.           if (aIID.equals(Ci.nsISupportsString)) return this;
  312.           throw Cr.NS_ERROR_NO_INTERFACE;
  313.         },
  314.         type: Ci.nsISupportsPrimitive.TYPE_STRING,
  315.         data: this._arr.shift(),
  316.         toString: function () { return this.data; }
  317.       };
  318.     }
  319.   };
  320.   for (var svcName in this.mDetectFiles) {
  321.     svcEnum._arr.push(svcName);
  322.   }
  323.   return svcEnum;
  324. }
  325.  
  326. flockWebDetective.prototype.getVersionForService =
  327. function flockWebDetective_getVersionForService(aServiceName)
  328. {
  329.   return (this.mVersions[aServiceName]) ? this.mVersions[aServiceName] : null;
  330. }
  331.  
  332. flockWebDetective.prototype.testDomain =
  333. function flockWebDetective_testDomain(aURL, aDomain)
  334. {
  335.   var uri = Cc["@mozilla.org/network/standard-url;1"]
  336.             .createInstance(Ci.nsIURI);
  337.   var host;
  338.   try {
  339.     uri.spec = aURL;
  340.     host = uri.host;
  341.   } catch (ex) {
  342.     // There wasn't a host
  343.     return false;
  344.   }
  345.   if (host == aDomain) {
  346.     return true;
  347.   }
  348.   var idx = host.indexOf("." + aDomain);
  349.   if (idx < 1) {
  350.     return false;
  351.   }
  352.   // We now know that the host contains .<domain> as a substring, and we just
  353.   // need to make sure that it properly ENDS with .<domain>
  354.   return ((idx + aDomain.length + 1) == host.length);
  355. }
  356. // END flockIWebDetective interface
  357.  
  358.  
  359. // BEGIN flockIMigratable
  360. flockWebDetective.prototype.__defineGetter__("migrationName",
  361.   function flockWebDetective_getter_migrationName() {
  362.     return "WebDetective";
  363.   }
  364. );
  365.  
  366. flockWebDetective.prototype.needsMigration =
  367. function flockWebDetective_needsMigration(aOldVersion)
  368. {
  369.   if (aOldVersion.substr(0, 3) === "0.9") {
  370.     // Migration from Cormorant
  371.     return true;
  372.   }
  373.   return false;
  374. };
  375.  
  376. flockWebDetective.prototype.startMigration =
  377. function flockWebDetective_startMigration(aOldVersion, aListener)
  378. {
  379.   var ctxt = {
  380.     oldVersion: aOldVersion,
  381.     listener: aListener
  382.   };
  383.  
  384.   if (aOldVersion.substr(0, 3) === "0.9") {
  385.     // Migration from Cormorant: need to get handles to the <app>/res/detect as
  386.     // well as the <profile>/detect directories so that we can copy files from
  387.     // the former to the latter.
  388.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"]
  389.                  .getService(Ci.nsIProperties);
  390.  
  391.     // Get the file spec for <app>/res/detect
  392.     ctxt.appDetectDir = dirSvc.get("ARes", Ci.nsIFile);
  393.     ctxt.appDetectDir.append("detect");
  394.  
  395.     // Get the file handle for <profile>/detect
  396.     ctxt.profDetectDir = dirSvc.get("ProfD", Ci.nsIFile);
  397.     ctxt.profDetectDir.append("detect");
  398.  
  399.     if (ctxt.profDetectDir.exists()) {
  400.       aListener.onUpdate(0, "Migrating Web Detective files");
  401.     }
  402.   }
  403.  
  404.   return { wrappedJSObject: ctxt };
  405. };
  406.  
  407. flockWebDetective.prototype.doMigrationWork =
  408. function flockWebDetective_doMigrationWork(aCtxtWrapper)
  409. {
  410.   var ctxt = aCtxtWrapper.wrappedJSObject;
  411.  
  412.   if (ctxt.oldVersion.substr(0, 3) === "0.9") {
  413.     // Migration from Cormorant: copy any .xml files from the <app>/res/detect
  414.     // directory into the user's <profile>/detect directory, clobbering any
  415.     // previous versions of those files that might exist there.
  416.     var appDetectDir = ctxt.appDetectDir;
  417.     var profDetectDir = ctxt.profDetectDir;
  418.  
  419.     if (!profDetectDir || !profDetectDir.exists()) {
  420.       return false;
  421.     }
  422.  
  423.     var dirEntries = appDetectDir.directoryEntries;
  424.     while (dirEntries.hasMoreElements()) {
  425.       var file = dirEntries.getNext().QueryInterface(Ci.nsIFile);
  426.  
  427.       if (file.isFile() &&
  428.           file.path.substr(file.path.length - 4).toLowerCase() === ".xml")
  429.       {
  430.         // 'file' now represents a new XML file we want to copy into the
  431.         // user's profile.  But we may need to delete the old version of the
  432.         // file first.
  433.         var oldFile = profDetectDir.clone();
  434.         oldFile.append(file.leafName);
  435.  
  436.         if (oldFile.exists()) {
  437.           DEBUG("removing old " + oldFile.leafName);
  438.           oldFile.remove(false); // Non-recursive
  439.         }
  440.  
  441.         DEBUG("copying " + file.leafName + " to " + profDetectDir.path);
  442.         file.copyTo(profDetectDir, null);
  443.       }
  444.     }
  445.   }
  446.  
  447.   return false;
  448. };
  449.  
  450. flockWebDetective.prototype.finishMigration =
  451. function flockWebDetective_finishMigration(aCtxtWrapper)
  452. {
  453. };
  454. // END flockIMigratable
  455.  
  456.  
  457. // BEGIN helper functions
  458. flockWebDetective.prototype.innerDetect =
  459. function flockWebDetective_innerDetect(aServiceName, aType, aDocument, aForm, aResults)
  460. {
  461.   if (!this.mEnabled) return false;
  462.   var ruleMatch = false;
  463.   var ruleDoc = this.mRules[aServiceName];
  464.   if (ruleDoc) {
  465.     // Cache the rules for faster retrieval next time
  466.     if (!ruleDoc.rules) {
  467.       ruleDoc.rules = [];
  468.     }
  469.     if (!ruleDoc.rules[aType]) {
  470.       ruleDoc.rules[aType] = this.getRulesOfType(ruleDoc, aType);
  471.     }
  472.     var rules = ruleDoc.rules[aType];
  473.     DEBUG("found "+rules.length+ " rule(s) of type '"+aType+"'");
  474.     for (var r = 0; (r < rules.length) && !ruleMatch; r++) {
  475.       var allCondsMatch = true;
  476.       if (rules[r].conditionsEl) {
  477.  
  478.         if (!rules[r].conditions) {
  479.           // This will be used for cacheing the conditions functions
  480.           rules[r].conditions = [];
  481.         }
  482.  
  483.         // First check the URL conditions
  484.         if (!rules[r].conditions["url"]) {
  485.           // Cache the URL conditions for faster retrieval next time
  486.           rules[r].conditions["url"] = this.getURLConditionsForRule(rules[r]);
  487.         }
  488.         var urlConds = rules[r].conditions["url"];
  489.         DEBUG("rule["+r+"]("+aType+") has "+urlConds.length+" URL condition(s)");
  490.         if (urlConds.length > 0) {
  491.           if (aDocument && aDocument instanceof Components.interfaces.nsIDOMHTMLDocument) {
  492.             var ios = Components.classes["@mozilla.org/network/io-service;1"]
  493.                                 .getService(Components.interfaces.nsIIOService);
  494.             var uri = ios.newURI(aDocument.URL, null, null);
  495.             for (var c = 0; (c < urlConds.length) && allCondsMatch; c++) {
  496.               if (!urlConds[c](uri)) {
  497.                 DEBUG("URL condition ["+c+"] failed");
  498.                 allCondsMatch = false;
  499.               }
  500.             }
  501.             if (!allCondsMatch) continue;
  502.           } else {
  503.             // There is no URL, so URL conditions automatically fail
  504.             allCondsMatch = false;
  505.           }
  506.         }
  507.         if (!allCondsMatch) continue;
  508.  
  509.         // Check Form conditions
  510.         if (!rules[r].conditions["form"]) {
  511.           // Cache the Form conditions for faster retrieval next time
  512.           rules[r].conditions["form"] = this.getFormConditionsForRule(rules[r], aForm);
  513.         }
  514.         var formConds = rules[r].conditions["form"];
  515.         DEBUG("rule["+r+"]("+aType+") has "+formConds.length+" Form condition(s)");
  516.         if (formConds.length > 0) {
  517.           if (aForm) {
  518.             for (var c = 0; (c < formConds.length) && allCondsMatch; c++) {
  519.               if (!formConds[c](aForm)) {
  520.                 DEBUG("Form condition ["+c+"] failed");
  521.                 allCondsMatch = false;
  522.               }
  523.             }
  524.           } else {
  525.             // There's no Form, so Form conditions automatically fail
  526.             allCondsMatch = false;
  527.           }
  528.         }
  529.         if (!allCondsMatch) continue;
  530.  
  531.         // Next check the Document conditions
  532.         if (!rules[r].conditions["doc"]) {
  533.           // Cache the Document conditions for faster retrieval next time
  534.           rules[r].conditions["doc"] = this.getDocConditionsForRule(rules[r]);
  535.         }
  536.         var docConds = rules[r].conditions["doc"];
  537.         DEBUG("rule["+r+"]("+aType+") has "+docConds.length+" Document condition(s)");
  538.         if (docConds.length > 0) {
  539.           if (aDocument) {
  540.             for (var c = 0; (c < docConds.length) && allCondsMatch; c++) {
  541.               if (!docConds[c](aDocument)) {
  542.                 DEBUG("Document condition ["+c+"] failed");
  543.                 allCondsMatch = false;
  544.               }
  545.             }
  546.           } else {
  547.             // There's no Document, so Document conditions automatically fail
  548.             allCondsMatch = false;
  549.           }
  550.         }
  551.         if (!allCondsMatch) continue;
  552.  
  553.         // Finally check the Cookie conditions
  554.         if (!rules[r].conditions["cookie"]) {
  555.           // Cache the Cookie conditions for faster retrieval next time
  556.           rules[r].conditions["cookie"] = this.getCookieConditionsForRule(rules[r]);
  557.         }
  558.         var cookieConds = rules[r].conditions["cookie"];
  559.         DEBUG("rule["+r+"]("+aType+") has "+cookieConds.length+" Cookie condition(s)");
  560.         if (cookieConds.length > 0) {
  561.           var relevantCookies = this.getRelevantCookiesForRule(rules[r]);
  562.           for (var c = 0; (c < cookieConds.length) && allCondsMatch; c++) {
  563.             if (!cookieConds[c](relevantCookies)) {
  564.               DEBUG("Cookie condition ["+c+"] failed");
  565.               allCondsMatch = false;
  566.             }
  567.           }
  568.         }
  569.       }
  570.  
  571.       if (allCondsMatch) {
  572.         DEBUG("All conditions matched for rule["+r+"]("+aType+")!");
  573.         ruleMatch = true;
  574.         if (!aResults) break;
  575.         // Now get results
  576.         this.getResultsForRule(aDocument, aForm, rules[r], aResults);
  577.       }
  578.     }
  579.   }
  580.   return ruleMatch;
  581. }
  582.  
  583. flockWebDetective.prototype.getRulesOfType =
  584. function flockWebDetective_getRulesOfType(aRulesDoc, aType)
  585. {
  586.   var rules = [];
  587.   var detectElements = aRulesDoc.getElementsByTagName("detect");
  588.   for (var i = 0; i < detectElements.length; i++) {
  589.     var detect = detectElements.item(i);
  590.     detect = detect.QueryInterface(Components.interfaces.nsIDOMElement);
  591.     if (aType == detect.getAttribute("type")) {
  592.       DEBUG("found <detect type='"+aType+"'>");
  593.       for (var j = 0; j < detect.childNodes.length; j++) {
  594.         var child = detect.childNodes.item(j);
  595.         try {
  596.           child.QueryInterface(Components.interfaces.nsIDOMElement);
  597.           if (child.tagName == "conditions") {
  598.             detect.conditionsEl = child;
  599.           }
  600.           if (child.tagName == "results") {
  601.             detect.resultsEl = child;
  602.           }
  603.         } catch (ex) {
  604.           // Do nothing
  605.         }
  606.       }
  607.       if (!detect.conditionsEl) {
  608.         detect.conditionsEl = detect;
  609.       }
  610.       if (!detect.resultsEl) {
  611.         detect.resultsEl = detect;
  612.       }
  613.       rules[rules.length] = detect;
  614.     }
  615.   }
  616.   return rules;
  617. }
  618.  
  619. flockWebDetective.prototype.getURLConditionsForRule =
  620. function flockWebDetective_getURLConditionsForRule(aRule)
  621. {
  622.   var conditions = [];
  623.  
  624.   // Look for the first "url" element (ignore any others)
  625.   var urlEls = aRule.conditionsEl.getElementsByTagName("url");
  626.   if (urlEls.length) {
  627.     var urlEl = urlEls.item(0);
  628.     conditions = this.addStandardConds(conditions, urlEl, function (aURI) { return aURI.spec; });
  629.     if (urlEl.hasAttribute("domain")) {
  630.       conditions[conditions.length] = function (aURI) {
  631.         var domain = urlEl.getAttribute("domain");
  632.         if (aURI.host == domain) return true;
  633.         var idx = aURI.host.indexOf("."+domain);
  634.         if (idx < 1) return false;
  635.         return ((idx + domain.length + 1) == aURI.host.length);
  636.       };
  637.     }
  638.     var tags = ["host", "path", "querystring"];
  639.     var tagFuncs = [];
  640.     tagFuncs["host"] = function (aURI) { return aURI.host; };
  641.     tagFuncs["path"] = function (aURI) { return aURI.path; };
  642.     tagFuncs["querystring"] = function (aURI) { return aURI.path.substring(aURI.path.indexOf("?")); };
  643.     for (var tag in tagFuncs) {
  644.       var elements = urlEl.getElementsByTagName(tag);
  645.       for (var e = 0; e < elements.length; e++) {
  646.         var elem = elements.item(e);
  647.         conditions = this.addStandardConds(conditions, elem, tagFuncs[tag]);
  648.       }
  649.     }
  650.     var regexpEls = urlEl.getElementsByTagName("regexp");
  651.     for (var i = 0; i < regexpEls.length; i++) {
  652.       var regexpEl = regexpEls.item(i);
  653.       var rExpr = this.getRegexpFromNode(regexpEl);
  654.       var inst = this;
  655.       conditions[conditions.length] = function (aURI) {
  656.         return inst.doRegexpMatch(aURI.spec, rExpr, null, null);
  657.       };
  658.     }
  659.   }
  660.   return conditions;
  661. }
  662.  
  663. flockWebDetective.prototype.getFormConditionsForRule =
  664. function flockWebDetective_getFormConditionsForRule(aRule, aForm)
  665. {
  666.   var conditions = [];
  667.  
  668.   // Look for the first "form" element (ignore any others)
  669.   var formEls = aRule.conditionsEl.getElementsByTagName("form");
  670.   if (formEls.length) {
  671.     var formEl = formEls.item(0);
  672.     // Iterate through all the children of "form"
  673.     for (var i = 0; i < formEl.childNodes.length; i++) {
  674.       var formChild = formEl.childNodes.item(i);
  675.       try {
  676.         formChild.QueryInterface(Components.interfaces.nsIDOMElement);
  677.       } catch (ex) {
  678.         continue;
  679.       }
  680.       DEBUG("Found <conditions><form><"+formChild.tagName+">");
  681.       switch (formChild.tagName) {
  682.         case "xpath":
  683.         {
  684.           conditions[conditions.length] = this.createXPathFormCondition(formChild);
  685.         }; break;
  686.         case "field":
  687.         {
  688.           conditions[conditions.length] = this.createFieldCondition(formChild);
  689.         }; break;
  690.       }
  691.     }
  692.   }
  693.   return conditions;
  694. }
  695.  
  696. flockWebDetective.prototype.createXPathFormCondition =
  697. function flockWebDetective_createXPathFormCondition(aXPathNode)
  698. {
  699.   var inst = this;
  700.   return function (aForm) {
  701.     if (!aXPathNode.xpathExpr) {
  702.       aXPathNode.xpathExpr = inst.getXPathExpression(aXPathNode);
  703.     }
  704.     if (!aXPathNode.xpathFunc) {
  705.       aXPathNode.xpathFunc = inst.createXPathCondition(aXPathNode.xpathExpr);
  706.     }
  707.     if (!aForm.xpathPrefix) {
  708.       aForm.xpathPrefix = inst.getXPathPrefix(aForm);
  709.       DEBUG("Got XPath prefix for this form: "+aForm.xpathPrefix);
  710.     }
  711.     return aXPathNode.xpathFunc(aForm.ownerDocument, aForm.xpathPrefix);
  712.   };
  713. }
  714.  
  715. const FIELD_ATTRIBUTES = [ "name", "type", "class" ];
  716.  
  717. flockWebDetective.prototype.createFieldCondition =
  718. function flockWebDetective_createFieldCondition(aFieldNode)
  719. {
  720.   var inst = this;
  721.   return function (aForm) {
  722.     return (inst.getMatchingFormField(aFieldNode, aForm) != null);
  723.   };
  724. }
  725.  
  726. flockWebDetective.prototype.getMatchingFormField =
  727. function flockWebDetective_getMatchingFormField(aFieldNode, aForm)
  728. {
  729.   var formFields = aForm.elements;
  730.   for (var i = 0; i < formFields.length; i++) {
  731.     var field = formFields.item(i)
  732.       .QueryInterface(Components.interfaces.nsIDOMElement);
  733.     if (aFieldNode.hasAttribute("tagname")) {
  734.       if (field.tagName.toLowerCase() != aFieldNode.getAttribute("tagname").toLowerCase()) {
  735.         // This form field does not match the pattern, so skip it
  736.         DEBUG("tagname does NOT match ["+field.tagName+" != "+aFieldNode.getAttribute("tagname")+"]");
  737.         continue;
  738.       }
  739.     }
  740.     if (aFieldNode.hasAttribute("fieldid")) {
  741.       if ( !field.hasAttribute("id") ||
  742.            (aFieldNode.getAttribute("fieldid") != field.getAttribute("id")) )
  743.       {
  744.         // This form field does not match the pattern, so skip it
  745.         DEBUG("fieldid does NOT match");
  746.         continue;
  747.       }
  748.       // The fields have matching ids!
  749.     }
  750.     var matchesAllAttributes = true;
  751.     for (var j = 0; j < FIELD_ATTRIBUTES.length; j++) {
  752.       var attr = FIELD_ATTRIBUTES[j];
  753.       if (aFieldNode.hasAttribute(attr)) {
  754.         if ( !field.hasAttribute(attr) ||
  755.              (aFieldNode.getAttribute(attr) != field.getAttribute(attr)) )
  756.         {
  757.           // This form field does not match the pattern, so skip it
  758.           DEBUG("'"+attr+"' does NOT match");
  759.           matchesAllAttributes = false;
  760.           break;
  761.         }
  762.       }
  763.     }
  764.     if (!matchesAllAttributes) continue;
  765.     // This form field matches the pattern
  766.     return field;
  767.   }
  768.   // Didn't find any fields matching the pattern
  769.   return null;
  770. }
  771.  
  772. flockWebDetective.prototype.getDocConditionsForRule =
  773. function flockWebDetective_getDocConditionsForRule(aRule)
  774. {
  775.   var conditions = [];
  776.  
  777.   // Document conditions can occur either under a "document" element, or else
  778.   // at the top level if compact syntax is being used.
  779.   var docCondNodes = this.getDocSubNodes(aRule.conditionsEl);
  780.  
  781.   // Iterate through all the "document" sub nodes
  782.   for (var i = 0; i < docCondNodes.length; i++) {
  783.     var docChild = docCondNodes[i];
  784.     try {
  785.       docChild.QueryInterface(Components.interfaces.nsIDOMElement);
  786.     } catch (ex) {
  787.       continue;
  788.     }
  789.     DEBUG("Found <conditions><document><"+docChild.tagName+">");
  790.     switch (docChild.tagName) {
  791.       case "xpath":
  792.       {
  793.         var xpathExpr = this.getXPathExpression(docChild);
  794.         if (xpathExpr) {
  795.           conditions[conditions.length] = this.createXPathCondition(xpathExpr);
  796.         }
  797.       }; break; // END "xpath" case
  798.  
  799.       case "regexp":
  800.       {
  801.         var rExpr = this.getRegexpFromNode(docChild);
  802.         if (this.isValidMatchRegexp(rExpr)) {
  803.           var isMultiLine = (docChild.getAttribute("multiline") == "true");
  804.           if (isMultiLine) {
  805.             conditions[conditions.length] =
  806.               this.createMultilineRegexpCondition(rExpr);
  807.           } else {
  808.             conditions[conditions.length] = this.createRegexpCondition(rExpr);
  809.           }
  810.         } else {
  811.           DEBUG("Condition will fail due to INVALID regexp: "+rExpr);
  812.           conditions[conditions.length] = function (aDocument) {
  813.             return false;
  814.           };
  815.         }
  816.       }; break; // END "regexp" case
  817.     }
  818.   }
  819.   return conditions;
  820. }
  821.  
  822. flockWebDetective.prototype.createXPathCondition =
  823. function flockWebDetective_createXPathCondition(aXPathExpr)
  824. {
  825.   var func = function (aDocument, aXPathPrefix) {
  826.     if (aDocument.noDOM) {
  827.       DEBUG("Document has no DOM, can't use XPath!!");
  828.       return false;
  829.     }
  830.     aDocument.QueryInterface(Components.interfaces.nsIDOMXPathEvaluator);
  831.     var allXPath = (aXPathPrefix) ? aXPathPrefix+aXPathExpr : aXPathExpr;
  832.     DEBUG("Evaluating XPath statement: "+allXPath);
  833.     var result;
  834.     if (aDocument instanceof Components.interfaces.nsIDOMHTMLDocument) {
  835.       result = aDocument.evaluate(allXPath, aDocument.body, null, 0, null);
  836.     } else {
  837.       DEBUG(aDocument.documentElement.namespaceURI);
  838.       var _xs = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
  839.                           .createInstance(Components.interfaces.nsIDOMSerializer);;
  840.       var xml = _xs.serializeToString(aDocument.documentElement);
  841.       var namespace = "xmlns=\""+aDocument.documentElement.namespaceURI+"\"";
  842.       var temp = xml.indexOf(namespace);
  843.       aDocument.doc = null;
  844.       //need to strip out the namespace otherwise aDocument.evaluate will fail
  845.       if (temp > 0) {
  846.         var new_xml = xml.substring(0, temp)
  847.                     + xml.substring((temp + namespace.length), xml.length);
  848.         var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
  849.                                .createInstance(Components.interfaces.nsIDOMParser);
  850.         aDocument.doc = parser.parseFromString(new_xml, "text/xml");
  851.       }
  852.       result = aDocument.evaluate( allXPath,
  853.                                    (aDocument.doc ? aDocument.doc : aDocument),
  854.                                    null,
  855.                                    Components.interfaces.nsIDOMXPathResult.ANY_TYPE,
  856.                                    null );
  857.     }
  858.     try {
  859.       result = result.QueryInterface(Components.interfaces.nsIDOMXPathResult);
  860.     } catch (ex) {
  861.       return false;
  862.     }
  863.     DEBUG(" - got an nsIDOMXPathResult of type ["+result.resultType+"]");
  864.     switch (result.resultType) {
  865.       case Components.interfaces.nsIDOMXPathResult.STRING_TYPE:
  866.       {
  867.         DEBUG("  result.stringValue = "+result.stringValue);
  868.         return true;
  869.       }; break;
  870.       case Components.interfaces.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE:
  871.       {
  872.         var node = result.iterateNext();
  873.         try {
  874.           node.QueryInterface(Components.interfaces.nsIDOMNode);
  875.           DEBUG(" - XPath matching node!" + node.nodeValue);
  876.           // Found a matching node!
  877.           return true;
  878.         } catch (ex) {
  879.           DEBUG(ex);
  880.           DEBUG(" - NO match on XPath " +result);
  881.         }
  882.         return false;
  883.       }; break;
  884.       default:
  885.         DEBUG("WARNING!!! Unhandled XPath result type: "+result.resultType);
  886.     }
  887.     return false;
  888.   };
  889.   return func;
  890. }
  891.  
  892. /**
  893.  * Generates a function for testing a regular expression against each line of
  894.  * a document in succession.
  895.  */
  896. flockWebDetective.prototype.createRegexpCondition =
  897. function flockWebDetective_createRegexpCondition(aRegExp)
  898. {
  899.   var inst = this;
  900.   var func = function createRegexpCond_inner(aDocument) {
  901.     var html = aDocument.documentElement.innerHTML;
  902.     var lines = html.split(/[\r\n]+/);
  903.     for (var i = 0; i < lines.length; i++) {
  904.       if (inst.doRegexpMatch(lines[i], aRegExp, null, null)) {
  905.         return true;
  906.       }
  907.     }
  908.     return false;
  909.   };
  910.   return func;
  911. }
  912.  
  913. /**
  914.  * Generates a function for testing a "multiline" regular expression against
  915.  * the entire text of a document.
  916.  */
  917. flockWebDetective.prototype.createMultilineRegexpCondition =
  918. function flockWebDetective_createMultilineRegexpCondition(aRegExp)
  919. {
  920.   var inst = this;
  921.   var func = function createMultilineRegexpCond_inner(aDocument) {
  922.     var html = aDocument.documentElement.innerHTML;
  923.     var oneString = html.replace(/[\r\n]/g, "");
  924.     return inst.doRegexpMatch(oneString, aRegExp, null, null);
  925.   };
  926.   return func;
  927. }
  928.  
  929. flockWebDetective.prototype.getCookieConditionsForRule =
  930. function flockWebDetective_getCookieConditionsForRule(aRule)
  931. {
  932.   var conditions = [];
  933.   var cookieEls = aRule.conditionsEl.getElementsByTagName("cookie");
  934.   for (var e = 0; e < cookieEls.length; e++) {
  935.     var cookieEl = cookieEls.item(e);
  936.     var cHost = null;
  937.     if (cookieEl.hasAttribute("host")) {
  938.       cHost = cookieEl.getAttribute("host");
  939.     }
  940.     var cName = null;
  941.     if (cookieEl.hasAttribute("name")) {
  942.       cName = cookieEl.getAttribute("name");
  943.     }
  944.     var cNoMatch = (cookieEl.getAttribute("nomatch") == "true");
  945.     this.addCookieCondition(conditions, cHost, cName, cNoMatch);
  946.   }
  947.   return conditions;
  948. }
  949.  
  950. flockWebDetective.prototype.addCookieCondition =
  951. function flockWebDetective_addCookieCondition(aConditions, aHost, aName, aNoMatch)
  952. {
  953.   aConditions[aConditions.length] = function (aRelevantCookies) {
  954.     var foundMatch = false;
  955.     for (var c = 0; (c < aRelevantCookies.length) && !foundMatch; c++) {
  956.       DEBUG("Testing cookie ["+aRelevantCookies[c].host+"]["+aRelevantCookies[c].name+"]");
  957.       DEBUG(" against rule ["+aNoMatch+"]["+aHost+"]["+aName+"]");
  958.       if ((aRelevantCookies[c].host == aHost) &&
  959.           (aRelevantCookies[c].name == aName))
  960.       {
  961.         foundMatch = true;
  962.       }
  963.     }
  964.     if (foundMatch && !aNoMatch) return true;
  965.     if (!foundMatch && aNoMatch) return true;
  966.     return false;
  967.   };
  968. }
  969.  
  970. flockWebDetective.prototype.getRelevantCookiesForRule =
  971. function flockWebDetective_getRelevantCookiesForRule(aRule)
  972. {
  973.   var relevant = [];
  974.   var cookieEls = aRule.conditionsEl.getElementsByTagName("cookie");
  975.   for (var e = 0; e < cookieEls.length; e++) {
  976.     var host = cookieEls.item(e).getAttribute("host");
  977.     var name = cookieEls.item(e).getAttribute("name");
  978.     //DEBUG("Cookies with host="+host+" and name="+name+" are relevant");
  979.     if (!relevant[host]) {
  980.       relevant[host] = [];
  981.     }
  982.     relevant[host][name] = true;
  983.   }
  984.   var relevantCookies = [];
  985.   var cookEnum = this.cookieMgr.enumerator;
  986.   var cookieCount = 0;
  987.   while (cookEnum.hasMoreElements()) {
  988.     var cookie = cookEnum.getNext()
  989.                          .QueryInterface(Components.interfaces.nsICookie);
  990.     cookieCount++;
  991.     //DEBUG(" - filtering cookie ["+cookie.host+"]["+cookie.name+"]");
  992.     if (relevant[cookie.host] && relevant[cookie.host][cookie.name]) {
  993.       relevantCookies[relevantCookies.length] = cookie;
  994.     }
  995.   }
  996.   DEBUG("Found "+relevantCookies.length+" relevant cookies out of "+cookieCount+" total");
  997.   return relevantCookies;
  998. }
  999.  
  1000. flockWebDetective.prototype.addStandardConds =
  1001. function flockWebDetective_addStandardConds(aConditionsArray, aDOMElement, paramFunc)
  1002. {
  1003.   aDOMElement.QueryInterface(Components.interfaces.nsIDOMElement);
  1004.   if (aDOMElement.hasAttribute("equals")) {
  1005.     aConditionsArray[aConditionsArray.length] = function (param) {
  1006.       var input = paramFunc(param);
  1007.       DEBUG("condition: "+input+" == "+aDOMElement.getAttribute("equals"));
  1008.       return (input == aDOMElement.getAttribute("equals"));
  1009.     };
  1010.   }
  1011.   if (aDOMElement.hasAttribute("contains")) {
  1012.     aConditionsArray[aConditionsArray.length] = function (param) {
  1013.       var input = paramFunc(param);
  1014.       DEBUG("condition: "+input+" ~= "+aDOMElement.getAttribute("contains"));
  1015.       return (input.indexOf(aDOMElement.getAttribute("contains")) != -1);
  1016.     };
  1017.   }
  1018.   return aConditionsArray;
  1019. }
  1020.  
  1021. flockWebDetective.prototype.getResultsForRule =
  1022. function flockWebDetective_getResultsForRule(aDocument, aForm, aRule, aResults)
  1023. {
  1024.   DEBUG(".getResultsForRule()");
  1025.   aResults.QueryInterface(Components.interfaces.nsIWritablePropertyBag2);
  1026.   if (!aRule.resultsEl) return;
  1027.  
  1028.   if (aDocument) {
  1029.  
  1030.     // Get URL results
  1031.     if (!aRule.urlResults) {
  1032.       aRule.urlResults = aRule.resultsEl.getElementsByTagName("url");
  1033.     }
  1034.     var workingData = {};
  1035.     for (var u = 0; u < aRule.urlResults.length; u++) {
  1036.       var urlEl = aRule.urlResults.item(u);
  1037.       DEBUG("found <results><url>");
  1038.       for (var i = 0; i < urlEl.childNodes.length; i++) {
  1039.         var child = urlEl.childNodes.item(i);
  1040.         try {
  1041.           child.QueryInterface(Components.interfaces.nsIDOMElement);
  1042.         } catch (ex) {
  1043.           continue;
  1044.         }
  1045.         DEBUG("found <results><url><"+child.tagName+">");
  1046.         switch (child.tagName) {
  1047.           case "regexp":
  1048.           {
  1049.             this.getRegexpResults(null, aDocument.URL, child, aResults, workingData);
  1050.           }; break;
  1051.         }
  1052.       }
  1053.     }
  1054.  
  1055.     // Get Document results
  1056.     workingData = {};
  1057.     if (!aRule.docResults) {
  1058.       aRule.docResults = this.getDocSubNodes(aRule.resultsEl);
  1059.     }
  1060.     for (var d = 0; d < aRule.docResults.length; d++) {
  1061.       var child = aRule.docResults[d];
  1062.       try {
  1063.         child.QueryInterface(Components.interfaces.nsIDOMElement);
  1064.       } catch (ex) {
  1065.         continue;
  1066.       }
  1067.       DEBUG("found <results><document><"+child.tagName+">");
  1068.       switch (child.tagName) {
  1069.         case "regexp":
  1070.         {
  1071.           this.getRegexpResults(aDocument, aDocument.documentElement.innerHTML, child, aResults, workingData);
  1072.         }; break;
  1073.         case "xpath":
  1074.         {
  1075.           this.getXPathResults(aDocument, child, aResults, workingData);
  1076.         }; break;
  1077.       }
  1078.     }
  1079.   }
  1080.  
  1081.   if (aForm) {
  1082.     // Get Form results
  1083.     var workingData = {};
  1084.     var formEls = aRule.resultsEl.getElementsByTagName("form");
  1085.     for (var f = 0; f < formEls.length; f++) {
  1086.       var formEl = formEls.item(f);
  1087.       DEBUG("found <results><form>");
  1088.       for (var i = 0; i < formEl.childNodes.length; i++) {
  1089.         var child = formEl.childNodes.item(i);
  1090.         try {
  1091.           child.QueryInterface(Components.interfaces.nsIDOMElement);
  1092.         } catch (ex) {
  1093.           continue;
  1094.         }
  1095.         DEBUG("found <results><form><"+child.tagName+">");
  1096.         switch (child.tagName) {
  1097.           case "xpath":
  1098.           {
  1099.             this.getXPathResults(aForm, child, aResults, workingData);
  1100.           }; break;
  1101.           case "field":
  1102.           {
  1103.             this.getFieldResults(aForm, child, aResults);
  1104.           }; break;
  1105.         }
  1106.       }
  1107.     }
  1108.   }
  1109.  
  1110.   // TODO: Get Cookie results...
  1111. }
  1112.  
  1113. flockWebDetective.prototype.getDocSubNodes =
  1114. function flockWebDetective_getDocSubNodes(aNode)
  1115. {
  1116.   var resultNodes = [];
  1117.   var documentEls = aNode.getElementsByTagName("document");
  1118.   for (var i = 0; i < documentEls.length; i++) {
  1119.     var docEl = documentEls.item(i);
  1120.     for (var j = 0; j < docEl.childNodes.length; j++) {
  1121.       var child = docEl.childNodes.item(j);
  1122.       try {
  1123.         child.QueryInterface(Components.interfaces.nsIDOMElement);
  1124.       } catch (ex) {
  1125.         continue;
  1126.       }
  1127.       resultNodes.push(child);
  1128.     }
  1129.   }
  1130.   // Any children of the result node that are NOT in [ conditions, url,
  1131.   // document, form, cookie ] are considered document conditions.
  1132.   for (var i = 0; i < aNode.childNodes.length; i++) {
  1133.     var child = aNode.childNodes.item(i);
  1134.     try {
  1135.       child.QueryInterface(Components.interfaces.nsIDOMElement);
  1136.     } catch (ex) {
  1137.       continue;
  1138.     }
  1139.     switch (child.tagName.toLowerCase()) {
  1140.       case "conditions":
  1141.       case "url":
  1142.       case "document":
  1143.       case "form":
  1144.       case "cookie":
  1145.         continue;
  1146.       default:
  1147.         resultNodes.push(child);
  1148.     }
  1149.   }
  1150.   return resultNodes;
  1151. }
  1152.  
  1153. flockWebDetective.prototype.getRegexpResults =
  1154. function flockWebDetective_getRegexpResults(aDecorEl, aString, aRegexpNode, aResults, aData)
  1155. {
  1156.   // Cache the regexp
  1157.   if (!aRegexpNode.rExpr) {
  1158.     aRegexpNode.rExpr = this.getRegexpFromNode(aRegexpNode);
  1159.   }
  1160.   // Cache whether it is valid
  1161.   if (!aRegexpNode.isValid) {
  1162.     aRegexpNode.isValid = this.isValidMatchRegexp(aRegexpNode.rExpr);
  1163.   }
  1164.   if (!aRegexpNode.isValid) {
  1165.     DEBUG("Can't get results due to INVALID regexp: "+aRegexpNode.rExpr);
  1166.     return;
  1167.   }
  1168.   // Cache the list of variables we are looking for
  1169.   if (!aRegexpNode.reVars) {
  1170.     aRegexpNode.reVars = [];
  1171.     aRegexpNode.postProcess = [];
  1172.     for (var re = 1; ; re++) {
  1173.       if (aRegexpNode.hasAttribute("re"+re)) {
  1174.         // Look for variable declarations as attributes
  1175.         aRegexpNode.reVars[re] = aRegexpNode.getAttribute("re"+re);
  1176.         continue;
  1177.       } else {
  1178.         // Look for variable declarations as elements
  1179.         var reTags = aRegexpNode.getElementsByTagName("re"+re);
  1180.         if (reTags.length) {
  1181.           var reTag = reTags.item(0);
  1182.           if (reTag.hasAttribute("name")) {
  1183.             aRegexpNode.reVars[re] = reTag.getAttribute("name");
  1184.             if (reTag.hasAttribute("processing")) {
  1185.               aRegexpNode.postProcess.push(reTag);
  1186.             }
  1187.             continue;
  1188.           }
  1189.         }
  1190.       }
  1191.       // There are no more variables to be gotten from this regexp
  1192.       break;
  1193.     }
  1194.   }
  1195.   // See if we have already decorated with the values for any of these vars
  1196.   var allDecorated = false;
  1197.   if (aDecorEl && aDecorEl._flock_decorations) {
  1198.     allDecorated = true;
  1199.     for (var i = 0; i < aRegexpNode.reVars.length; i++) {
  1200.       var val = aDecorEl._flock_decorations[aRegexpNode.reVars[i]];
  1201.       if (val) {
  1202.         DEBUG("Found a decoration for ["+aRegexpNode.reVars[i]+"] = "+val);
  1203.         aResults.setPropertyAsAString(aRegexpNode.reVars[i], val);
  1204.       } else {
  1205.         allDecorated = false;
  1206.       }
  1207.     }
  1208.   }
  1209.   if (allDecorated) {
  1210.     DEBUG("Found all the vars as decorations! No need to run regexp...");
  1211.     return;
  1212.   }
  1213.   // Ensure that a value exists -- at least an empty string -- for each var
  1214.   for (var i = 0; i < aRegexpNode.reVars.length; i++) {
  1215.     try {
  1216.       aResults.getPropertyAsAString(aRegexpNode.reVars[i]);
  1217.     } catch (ex) {
  1218.       aResults.setPropertyAsAString(aRegexpNode.reVars[i], "");
  1219.     }
  1220.   }
  1221.   DEBUG("Getting results using regexp: "+aRegexpNode.rExpr);
  1222.   if (aRegexpNode.rExpr) {
  1223.     var isMultiLine = (aRegexpNode.getAttribute("multiline") == "true");
  1224.     var isMultiValent = (aRegexpNode.getAttribute("multivalent") == "true");
  1225.     if (isMultiLine) {
  1226.       if (!aData.oneString) {
  1227.         aData.oneString = aString.replace(/[\r\n]/g, "");
  1228.       }
  1229.       this.doRegexpMatch( aData.oneString,
  1230.                           aRegexpNode.rExpr,
  1231.                           aRegexpNode.reVars,
  1232.                           aResults,
  1233.                           aDecorEl );
  1234.     } else {
  1235.       // isMultiLine == false
  1236.       if (!aData.lines) {
  1237.         aData.lines = aString.split(/[\r\n]/);
  1238.       }
  1239.       DEBUG("There are "+aData.lines.length+" lines to test with regexp");
  1240.       for (var line = 0; line < aData.lines.length; line++) {
  1241.         //DEBUG("line "+line+" has "+aData.lines[line].length+" characters");
  1242.         if (this.doRegexpMatch( aData.lines[line],
  1243.                                 aRegexpNode.rExpr,
  1244.                                 aRegexpNode.reVars,
  1245.                                 aResults,
  1246.                                 aDecorEl,
  1247.                                 isMultiValent ))
  1248.         {
  1249.           for (var i = 0; i < aRegexpNode.postProcess.length; i++) {
  1250.             this.postProcess(aRegexpNode.postProcess[i], aResults, aDecorEl);
  1251.           }
  1252.           break;
  1253.         }
  1254.       }
  1255.     }
  1256.   }
  1257. }
  1258.  
  1259. flockWebDetective.prototype.setResult =
  1260. function flockWebDetective_setResult(aResults, aName, aValue, aDecorEl)
  1261. {
  1262.   aResults.setPropertyAsAString(aName, aValue);
  1263.   if (aDecorEl) {
  1264.     if (!aDecorEl._flock_decorations) {
  1265.       aDecorEl._flock_decorations = [];
  1266.     }
  1267.     aDecorEl._flock_decorations[aName] = aValue;
  1268.   }
  1269. }
  1270.  
  1271. flockWebDetective.prototype.postProcess =
  1272. function flockWebDetective_postProcess(aRENode, aResults, aDecorEl)
  1273. {
  1274.   var name = aRENode.getAttribute("name");
  1275.   DEBUG(".postProcess('"+name+"')");
  1276.   var processes = aRENode.getAttribute("processing").split(",");
  1277.   for (var i = 0; i < processes.length; i++) {
  1278.     var oldVal = aResults.getPropertyAsAString(name);
  1279.     var newVal = oldVal;
  1280.     DEBUG(" - process: "+processes[i]);
  1281.     switch (processes[i].toLowerCase()) {
  1282.       case "unescape":
  1283.         newVal = unescape(oldVal);
  1284.         break;
  1285.       case "toupper":
  1286.       case "touppercase":
  1287.         newVal = oldVal.toUpperCase();
  1288.         break;
  1289.       case "tolower":
  1290.       case "tolowercase":
  1291.         newVal = oldVal.toLowerCase();
  1292.         break;
  1293.       case "subst":
  1294.         // There must be a CDATA block with the substitution regexp
  1295.         var substRegexp = null;
  1296.         for (var j = 0; (j < aRENode.childNodes.length) && !substRegexp; j++) {
  1297.           var child = aRENode.childNodes.item(j);
  1298.           if (child.nodeType ==
  1299.               Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE)
  1300.           {
  1301.             DEBUG("  - substitution regexp: "+child.nodeValue);
  1302.             substRegexp = this.parseSubstRegexp(child.nodeValue);
  1303.           }
  1304.         }
  1305.         if (substRegexp) {
  1306.           DEBUG("   pattern: "+substRegexp.pattern);
  1307.           DEBUG("   replacement: "+substRegexp.replace);
  1308.           // Convert to JS-compatible syntax
  1309.           var replacement = substRegexp.replace;
  1310.           for (var r = 1; r < 10; r++) {
  1311.             var replacementRE = "/\/"+r+"/g";
  1312.             replacement = replacement.replace(eval(replacementRE), "$"+r);
  1313.           }
  1314.           // Do the substitution
  1315.           newVal = oldVal.replace(eval(substRegexp.pattern), replacement);
  1316.         }
  1317.         break;
  1318.       default:
  1319.         DEBUG("WARNING: unhandled processing directive: "+processes[i]);
  1320.     }
  1321.     DEBUG("   - new value: "+newVal);
  1322.     this.setResult(aResults, name, newVal, aDecorEl);
  1323.   }
  1324. }
  1325.  
  1326. flockWebDetective.prototype.parseSubstRegexp =
  1327. function flockWebDetective_parseSubstRegexp(aRegexpString)
  1328. {
  1329.   if (!aRegexpString.match(/^s\/((\\\/|[^\/])+)\/((\\\/|[^\/])*)\/[gim]{0,3}$/)) {
  1330.     DEBUG("INVALID substitution regexp: "+aRegexpString);
  1331.     return null;
  1332.   }
  1333.   return {
  1334.     pattern: "/"+RegExp.$1+"/",
  1335.     replace: RegExp.$3
  1336.   };
  1337. }
  1338.  
  1339. const TEST_FOR_ATTRIBS = [
  1340.   "class",
  1341.   "name",
  1342.   "value",
  1343.   "action",
  1344.   "method"
  1345. ];
  1346.  
  1347. flockWebDetective.prototype.getXPathPrefix =
  1348. function flockWebDetective_getXPathPrefix(aHTMLElement)
  1349. {
  1350.   if (!aHTMLElement) return "";
  1351.   DEBUG(".getXPathPrefix('"+aHTMLElement+"')");
  1352.   var el = aHTMLElement.QueryInterface(Components.interfaces.nsIDOMElement);
  1353.   var doc = el.ownerDocument;
  1354.   var xpathExpr = "//"+el.tagName.toLowerCase();
  1355.   DEBUG(".getXPathPrefix" + xpathExpr);
  1356.   if (el.hasAttribute("id")) {
  1357.     xpathExpr += "[@id=\""+el.getAttribute("id")+"\"]";
  1358.   } else {
  1359.     for (var i = 0; i < TEST_FOR_ATTRIBS.length; i++) {
  1360.       var attrib = TEST_FOR_ATTRIBS[i];
  1361.       if (el.hasAttribute(attrib)) {
  1362.         xpathExpr += "[@"+attrib+"=\""+el.getAttribute(attrib)+"\"]";
  1363.       }
  1364.     }
  1365.     // This may not be enough to uniquely identify the node, so do the parent
  1366.     // as well...
  1367.     if (el.parentNode && (el.parentNode.tagName.toLowerCase() != "body")) {
  1368.       xpathExpr = this.getXPathPrefix(el.parentNode) + xpathExpr.substring(1);
  1369.     }
  1370.   }
  1371.   return xpathExpr;
  1372. }
  1373.  
  1374. flockWebDetective.prototype.getXPathExpression =
  1375. function flockWebDetective_getXPathExpression(aXPathNode)
  1376. {
  1377.   var xpathExpr = null;
  1378.   if (aXPathNode.hasAttribute("match")) {
  1379.     xpathExpr = aXPathNode.getAttribute("match");
  1380.   } else {
  1381.     for (var j = 0; (j < aXPathNode.childNodes.length) && !xpathExpr; j++) {
  1382.       if ( aXPathNode.childNodes.item(j).nodeType ==
  1383.            Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE )
  1384.       {
  1385.         xpathExpr = aXPathNode.childNodes.item(j).nodeValue;
  1386.       }
  1387.     }
  1388.   }
  1389.   return xpathExpr;
  1390. }
  1391.  
  1392. flockWebDetective.prototype.getXPathResults =
  1393. function flockWebDetective_getXPathResults(aDocumentOrElement, aXPathNode, aResults, aData)
  1394. {
  1395.   // First check to see if we have already cached this result as a decoration
  1396.   // on aDocumentOrElement
  1397.   var name = null;
  1398.   if (aXPathNode.hasAttribute("name")) {
  1399.     name = aXPathNode.getAttribute("name");
  1400.     if ( aDocumentOrElement._flock_decorations &&
  1401.          aDocumentOrElement._flock_decorations[name] )
  1402.     {
  1403.       DEBUG("Using decorated value for '"+name+"'");
  1404.       aResults.setPropertyAsAString(name, aDocumentOrElement._flock_decorations[name]);
  1405.       return;
  1406.     }
  1407.   }
  1408.   // No cached value, so press on
  1409.   var doc;
  1410.   var needPrefix = false;
  1411.   try {
  1412.     aDocumentOrElement.QueryInterface(Components.interfaces.nsIDOMDocument);
  1413.     doc = aDocumentOrElement;
  1414.   } catch (ex) {
  1415.     doc = aDocumentOrElement.ownerDocument;
  1416.     needPrefix = true;
  1417.   }
  1418.   if (!aDocumentOrElement.xpathPrefix && needPrefix) {
  1419.     aDocumentOrElement.xpathPrefix = this.getXPathPrefix(aDocumentOrElement);
  1420.   }
  1421.   if (!aDocumentOrElement.xpathPrefix) {
  1422.     aDocumentOrElement.xpathPrefix = "";
  1423.   }
  1424.   if (!aXPathNode.xpathExpr) {
  1425.     aXPathNode.xpathExpr = this.getXPathExpression(aXPathNode);
  1426.   }
  1427.   DEBUG("aDocumentOrElement.xpathPrefix " + aDocumentOrElement.xpathPrefix);
  1428.   var xpathExpr = aXPathNode.xpathExpr;
  1429.   if (aDocumentOrElement.xpathPrefix) {
  1430.     xpathExpr = aDocumentOrElement.xpathPrefix + aXPathNode.xpathExpr;
  1431.   }
  1432.   if (xpathExpr) {
  1433.     var extract = null;
  1434.     if (aXPathNode.hasAttribute("extract")) {
  1435.       extract = aXPathNode.getAttribute("extract");
  1436.     }
  1437.     var multivalent = false;
  1438.     if (aXPathNode.hasAttribute("multivalent")) {
  1439.       multivalent = aXPathNode.getAttribute("multivalent");
  1440.     }
  1441.     var snippets = this.getXMLSnippetsFromXPath(doc, xpathExpr, extract);
  1442.     DEBUG(" XPath got us "+snippets.length+" snippets to check " + multivalent);
  1443.     if (snippets.length && name) {
  1444.       if (!multivalent) {
  1445.         this.setResult(aResults, name, snippets[0], aDocumentOrElement);
  1446.       } else {
  1447.         this.setResult(aResults, name, snippets, aDocumentOrElement);
  1448.       }
  1449.     }
  1450.     // Now let's see if there's a regexp subnode
  1451.     for (var i = 0; i < aXPathNode.childNodes.length; i++) {
  1452.       var child = aXPathNode.childNodes.item(i);
  1453.       try {
  1454.         child.QueryInterface(Components.interfaces.nsIDOMElement);
  1455.       } catch (ex) {
  1456.         continue;
  1457.       }
  1458.       if (child.tagName == "regexp") {
  1459.         DEBUG(" This XPath statement has a Regexp too!");
  1460.         for (var j = 0; j < snippets.length; j++) {
  1461.           this.getRegexpResults(aDocumentOrElement, snippets[j], child, aResults, aData);
  1462.         }
  1463.       }
  1464.     }
  1465.   }
  1466. }
  1467.  
  1468. const VALID_EXTRACT_VALUES = {
  1469.   "value": true,
  1470.   "nodeValue": true,
  1471. };
  1472.  
  1473. flockWebDetective.prototype.getXMLSnippetsFromXPath =
  1474. function flockWebDetective_getXMLSnippetsFromXPath(aDocument, aXPathExpr, aExtract)
  1475. {
  1476.   DEBUG("Looking for snippets that match this XPath: "+aXPathExpr+" "+aDocument);
  1477.   var snippets = [];
  1478.   if (aDocument.noDOM) {
  1479.     DEBUG("Document has no DOM, can't use XPath!!");
  1480.     return;
  1481.   }
  1482.   aDocument.QueryInterface(Components.interfaces.nsIDOMXPathEvaluator);
  1483.   var result;
  1484.   if (aDocument instanceof Components.interfaces.nsIDOMHTMLDocument) {
  1485.     result = aDocument.evaluate(aXPathExpr, aDocument.body, null, 0, null);
  1486.   } else {
  1487.     result = aDocument.evaluate( aXPathExpr,
  1488.                                  (aDocument.doc ? aDocument.doc : aDocument),
  1489.                                  null,
  1490.                                  Components.interfaces.nsIDOMXPathResult.ANY_TYPE,
  1491.                                  null );
  1492.   }
  1493.   try {
  1494.     result = result.QueryInterface(Components.interfaces.nsIDOMXPathResult);
  1495.   } catch (ex) {
  1496.     return;
  1497.   }
  1498.   switch (result.resultType) {
  1499.     case Components.interfaces.nsIDOMXPathResult.STRING_TYPE:
  1500.     {
  1501.       DEBUG("  result.stringValue = "+result.stringValue);
  1502.       snippets.push(result.stringValue);
  1503.     }; break;
  1504.     case Components.interfaces.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE:
  1505.     {
  1506.       var node;
  1507.       while (node = result.iterateNext()) {
  1508.         try {
  1509.           node.QueryInterface(Components.interfaces.nsIDOMNode);
  1510.         } catch (ex) {
  1511.           continue;
  1512.         }
  1513.         var value;
  1514.         if ( node.hasChildNodes() &&
  1515.              (node.childNodes.length > 1
  1516.               || (node.childNodes.length == 1
  1517.                 && !node.firstChild instanceof Components.interfaces.nsIDOMText)) )
  1518.         {
  1519.           var _xs = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
  1520.                               .createInstance(Components.interfaces.nsIDOMSerializer);
  1521.           value = _xs.serializeToString(node);
  1522.         } else if ( node.childNodes.length == 1 &&
  1523.                     node.firstChild instanceof Components.interfaces.nsIDOMText )
  1524.         {
  1525.           value = node.firstChild.textContent;
  1526.         } else {
  1527.           value = node.nodeValue;
  1528.         }
  1529.         if (aExtract) {
  1530.           if (VALID_EXTRACT_VALUES[aExtract]) {
  1531.             value = node[aExtract];
  1532.           } else if ( aExtract.indexOf("attribute:") == 0 &&
  1533.                       node instanceof Components.interfaces.nsIDOMElement )
  1534.           {
  1535.             var attrName = aExtract.substring(10);
  1536.             value = node.getAttribute(attrName);
  1537.           }
  1538.         }
  1539.         DEBUG(" - XPath matching node: "+ value);
  1540.         snippets.push(value);
  1541.       }
  1542.     }; break;
  1543.     default:
  1544.       DEBUG("WARNING!!! Unhandled XPath result type: "+result.resultType);
  1545.   }
  1546.   return snippets;
  1547. }
  1548.  
  1549. flockWebDetective.prototype.getFieldResults =
  1550. function flockWebDetective_getFieldResults(aForm, aFieldNode, aResults)
  1551. {
  1552.   var varname = null;
  1553.   if (aFieldNode.hasAttribute("extractas")) {
  1554.     varname = aFieldNode.getAttribute("extractas");
  1555.   } else if (aFieldNode.hasAttribute("name")) {
  1556.     varname = aFieldNode.getAttribute("name");
  1557.   } else if (aFieldNode.hasAttribute("fieldid")) {
  1558.     varname = aFieldNode.getAttribute("fieldid");
  1559.   }
  1560.   if (varname) {
  1561.     var field = this.getMatchingFormField(aFieldNode, aForm);
  1562.     if (field instanceof Components.interfaces.nsIDOMHTMLInputElement) {
  1563.       field.QueryInterface(Components.interfaces.nsIDOMHTMLInputElement);
  1564.       DEBUG(".getFieldResults() - extracting form field value ["+varname+"] = "+field.value);
  1565.       aResults.setPropertyAsAString(varname, field.value);
  1566.     }
  1567.   }
  1568. }
  1569.  
  1570. flockWebDetective.prototype.getRegexpFromNode =
  1571. function flockWebDetective_getRegexpFromNode(aRegexpNode)
  1572. {
  1573.   var rExpr = null;
  1574.   if (aRegexpNode.hasAttribute("expression")) {
  1575.     rExpr = aRegexpNode.getAttribute("expression");
  1576.   } else {
  1577.     for (var j = 0; j < aRegexpNode.childNodes.length; j++) {
  1578.       if ( aRegexpNode.childNodes.item(j).nodeType ==
  1579.            Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE )
  1580.       {
  1581.         rExpr = aRegexpNode.childNodes.item(j).nodeValue;
  1582.       }
  1583.     }
  1584.   }
  1585.   return rExpr;
  1586. }
  1587.  
  1588. /**
  1589.  * This does not do 'true' regexp validation, but rather just enough to ensure
  1590.  * that executing this string will not allow arbitrary code execution.
  1591.  */
  1592. flockWebDetective.prototype.isValidMatchRegexp =
  1593. function flockWebDetective_isValidMatchRegexp(aRegexpString)
  1594. {
  1595.   DEBUG("Testing for validity: "+aRegexpString);
  1596.   return aRegexpString.match(/^\/(\\\/|[^\/])+\/[gim]{0,3}$/);
  1597. }
  1598.  
  1599. flockWebDetective.prototype.doRegexpMatch =
  1600. function flockWebDetective_doRegexpMatch( aString, aRegexp, aResultFields,
  1601.                                           aResults, aDecorEl, aIsMultiValent )
  1602. {
  1603.   //DEBUG(aString);
  1604.   if (aString.match(eval(aRegexp))) {
  1605.     DEBUG(".doRegexpMatch() MATCH!");
  1606.     if (aResultFields && aResults) {
  1607.       for (var i = 1; i < aResultFields.length; i++) {
  1608.         var fieldName = aResultFields[i];
  1609.         if (aIsMultiValent) {
  1610.           for (var j = 1; ; j++) {
  1611.             try {
  1612.               aResults.getPropertyAsAString(fieldName+j);
  1613.             } catch (ex) {
  1614.               fieldName += j;
  1615.               break;
  1616.             }
  1617.           }
  1618.         }
  1619.         DEBUG(".doRegexpMatch() found [ "+fieldName+" ] = "+eval("RegExp.$"+i));
  1620.         this.setResult(aResults, fieldName, eval("RegExp.$"+i), aDecorEl);
  1621.       }
  1622.     }
  1623.     return true;
  1624.   }
  1625.   //DEBUG(".doRegexpMatch() NO-match.");
  1626.   return false;
  1627. }
  1628.  
  1629. flockWebDetective.prototype.loadStrings =
  1630. function flockWebDetective_loadStrings(aServiceName)
  1631. {
  1632.   this.mStrings[aServiceName] = [];
  1633.   var stringsEls = this.mRules[aServiceName].getElementsByTagName("strings");
  1634.   for (var i = 0; i < stringsEls.length; i++) {
  1635.     var stringsEl = stringsEls.item(i);
  1636.     stringsEl.QueryInterface(Components.interfaces.nsIDOMElement);
  1637.     var stringEls = stringsEl.getElementsByTagName("string");
  1638.     for (var j = 0; j < stringEls.length; j++) {
  1639.       var stringEl = stringEls.item(j);
  1640.       stringEl.QueryInterface(Components.interfaces.nsIDOMElement);
  1641.       if (stringEl.hasAttribute("name")) {
  1642.         var stringName = stringEl.getAttribute("name");
  1643.         var foundValue = null;
  1644.         if (stringEl.hasAttribute("value")) {
  1645.           foundValue = stringEl.getAttribute("value");
  1646.         }
  1647.         var longestTextValue = "";
  1648.         for (var k = 0; (k < stringEl.childNodes.length) && !foundValue; k++) {
  1649.           var child = stringEl.childNodes.item(k);
  1650.           switch (child.nodeType) {
  1651.             case Components.interfaces.nsIDOMNode.TEXT_NODE:
  1652.               if (child.nodeValue.length > longestTextValue.length) {
  1653.                 longestTextValue = child.nodeValue;
  1654.               }
  1655.               break;
  1656.             case Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE:
  1657.               foundValue = stringEl.childNodes.item(k).nodeValue;
  1658.               break;
  1659.           }
  1660.         }
  1661.         if (!foundValue) {
  1662.           foundValue = longestTextValue;
  1663.         }
  1664.         this.mStrings[aServiceName][stringName] = foundValue;
  1665.         DEBUG( ".loadStrings('"+aServiceName+"'): found string '"+stringName
  1666.                +"': "+this.mStrings[aServiceName][stringName] );
  1667.       }
  1668.     }
  1669.   }
  1670. }
  1671.  
  1672. flockWebDetective.prototype.loadSessionCookies =
  1673. function flockWebDetective_loadSessionCookies(aServiceName)
  1674. {
  1675.   this.mSessionCookies[aServiceName] = [];
  1676.   var cookiesEls = this.mRules[aServiceName].getElementsByTagName("sessioncookies");
  1677.   for (var i = 0; i < cookiesEls.length; i++) {
  1678.     var cookiesEl = cookiesEls.item(i);
  1679.     cookiesEl.QueryInterface(Components.interfaces.nsIDOMElement);
  1680.     var cookieEls = cookiesEl.getElementsByTagName("cookie");
  1681.     for (var j = 0; j < cookieEls.length; j++) {
  1682.       var cookieEl = cookieEls.item(j);
  1683.       cookieEl.QueryInterface(Components.interfaces.nsIDOMElement);
  1684.       var c = {
  1685.         host: cookieEl.getAttribute("host"),
  1686.         name: cookieEl.getAttribute("name"),
  1687.         path: cookieEl.getAttribute("path")
  1688.       };
  1689.       DEBUG( ".loadSessionCookies('"+aServiceName+"'): host["+c.host+"] name["
  1690.              +c.name+"] path["+c.path+"]" );
  1691.       this.mSessionCookies[aServiceName].push(c);
  1692.     }
  1693.   }
  1694. }
  1695. // END helper functions
  1696.  
  1697. // BEGIN update service functions
  1698.  
  1699. // This is patterned after nsSearchService.js:engineMetadataService
  1700. function makeURI(url) {
  1701.   var ios = Cc['@mozilla.org/network/io-service;1']
  1702.     .getService(Ci.nsIIOService);
  1703.   try {
  1704.     return ios.newURI(url, null, null);
  1705.   } catch (ex) { }
  1706.  
  1707.   return null;
  1708. }
  1709.  
  1710. function createStatement(dbconn, sql) {
  1711.   var stmt = dbconn.createStatement(sql);
  1712.   var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"]
  1713.     .createInstance(Ci.mozIStorageStatementWrapper);
  1714.  
  1715.   wrapper.initialize(stmt);
  1716.   return wrapper;
  1717. }
  1718.  
  1719. function UpdateMetadataStore() {
  1720.   this.init();
  1721. }
  1722.  
  1723. UpdateMetadataStore.prototype = {
  1724.   init: function UMS_init() {
  1725.     var dbfile = Cc['@mozilla.org/file/directory_service;1']
  1726.       .getService(Ci.nsIProperties).get('ProfD', Ci.nsIFile);
  1727.     dbfile.append('webdetective.sqlite');
  1728.  
  1729.     var storageService = Cc['@mozilla.org/storage/service;1']
  1730.       .getService(Ci.mozIStorageService);
  1731.     this.mDBConn = storageService.openDatabase(dbfile);
  1732.  
  1733.     var schema = 'id INTEGER PRIMARY KEY, servicename STRING, ' +
  1734.                  'name STRING, value STRING';
  1735.  
  1736.     try {
  1737.       this.mDBConn.createTable('webdetect_data', schema);
  1738.     }
  1739.     catch (e) { }
  1740.  
  1741.     this.mGetData = createStatement(this.mDBConn,
  1742.       'SELECT value FROM webdetect_data WHERE servicename = :servicename ' +
  1743.       'AND name = :name');
  1744.     this.mDeleteData = createStatement(this.mDBConn,
  1745.       'DELETE FROM webdetect_data WHERE servicename = :servicename ' +
  1746.       'AND name = :name');
  1747.     this.mInsertData = createStatement(this.mDBConn,
  1748.       'INSERT INTO webdetect_data (servicename, name, value) ' +
  1749.       'VALUES (:servicename, :name, :value)');
  1750.   },
  1751.   getAttr: function UMS_getAttr(serviceName, name) {
  1752.     name = name.toLowerCase();
  1753.  
  1754.     var stmt = this.mGetData;
  1755.     stmt.reset();
  1756.     var pp = stmt.params;
  1757.     pp.servicename = serviceName;
  1758.     pp.name = name;
  1759.  
  1760.     var value = null;
  1761.     if (stmt.step())
  1762.       value = stmt.row.value;
  1763.     stmt.reset();
  1764.     return value;
  1765.   },
  1766.   setAttr: function UMS_setAttr(serviceName, name, value) {
  1767.     name = name.toLowerCase();
  1768.  
  1769.     this.mDBConn.beginTransaction();
  1770.  
  1771.     this.deleteServiceData(serviceName, name);
  1772.  
  1773.     pp = this.mInsertData.params;
  1774.     pp.servicename = serviceName;
  1775.     pp.name = name;
  1776.     pp.value = value;
  1777.     this.mInsertData.step();
  1778.     this.mInsertData.reset();
  1779.  
  1780.     this.mDBConn.commitTransaction();
  1781.   },
  1782.   deleteServiceData: function UMS_deleteServiceData(serviceName, name) {
  1783.     name = name.toLowerCase();
  1784.  
  1785.     var pp = this.mDeleteData.params;
  1786.     pp.servicename = serviceName;
  1787.     pp.name = name;
  1788.     this.mDeleteData.step();
  1789.     this.mDeleteData.reset();
  1790.   },
  1791. }
  1792.  
  1793. flockWebDetective.prototype.startUpdateService =
  1794. function flockWebDetective_startUpdateService()
  1795. {
  1796.   this.updateMetadataStore = new UpdateMetadataStore();
  1797.  
  1798.   var tm = Cc['@mozilla.org/updates/timer-manager;1']
  1799.     .getService(Ci.nsIUpdateTimerManager);
  1800.  
  1801.   var prefs = Cc['@mozilla.org/preferences-service;1']
  1802.     .getService(Ci.nsIPrefBranch)
  1803.   var interval = prefs.getIntPref('flock.service.webdetective.updateinterval');
  1804.  
  1805.   var seconds = interval * 3600;
  1806.   tm.registerTimer('web-detective-update-timer', this, seconds);
  1807. }
  1808.  
  1809. /**
  1810.  * Stevo : This may be a bit clunky right now, we have 2 variables (updatesOk and fileCount) that
  1811.  *  are used to maintain if all updates are successful, and when to report back the results to aListener
  1812.  *  if aListener is valid. So for each service we send off a request and have a listener that onSuccess
  1813.  *  decrements the fileCount and when fileCount is <= 0 calls the onSuccess or onError depending on the
  1814.  *  state of updatesOk, onError will decrement fileCount and set updatesOk to false and when fileCount is <= 0
  1815.  *  it will report back onError to the aListener.
  1816.  */
  1817. flockWebDetective.prototype.checkForUpdates =
  1818. function flockWebDetective_checkForUpdates(/*boolean */forceUpdates, /*flockIListener */aListener)
  1819. {
  1820.   var now = Date.now();
  1821.   var updatesOk = true;  // If any updates fail then this will be false.
  1822.   var fileCount = 0;
  1823.  
  1824.   var serviceUpdateListener = {
  1825.     onSuccess : function(aSubject, aTopic) {
  1826.       fileCount = fileCount - 1;
  1827.       if (fileCount <= 0) {
  1828.         if (aListener) {
  1829.           if (updatesOk) {
  1830.             aListener.onSuccess(null, null);
  1831.           } else {
  1832.             aListener.onError(null, null, null);
  1833.           }
  1834.         }
  1835.       }
  1836.     },
  1837.     onError : function(aSubject, aTopic, aError) {
  1838.       fileCount = fileCount - 1;
  1839.       updatesOk = false;
  1840.       if (fileCount <= 0) {
  1841.         if (aListener) {
  1842.           aListener.onError(null, null, null);
  1843.         }
  1844.       }
  1845.     }
  1846.   };
  1847.  
  1848.   for (var serviceName in this.mDetectFiles) {
  1849.     var expire = this.updateMetadataStore.getAttr(serviceName, 'updateexpire');
  1850.     if ((expire && expire > now) && !forceUpdates)
  1851.       continue;
  1852.  
  1853.     fileCount = fileCount + 1;
  1854.     if (aListener) {
  1855.       aListener.onStart(null, serviceName);
  1856.       this.sendUpdateRequest(serviceName, serviceUpdateListener);
  1857.     } else {
  1858.       this.sendUpdateRequest(serviceName);
  1859.     }
  1860.   }
  1861. }
  1862.  
  1863. flockWebDetective.prototype.getUpdateInfo =
  1864. function flockWebDetective_getUpdateInfo(aServiceName)
  1865. {
  1866.   var updateEls = this.mRules[aServiceName].getElementsByTagName('update');
  1867.   for (var i = 0; i < updateEls.length; i++) {
  1868.     var updateEl = updateEls.item(i);
  1869.     updateEl.QueryInterface(Ci.nsIDOMElement);
  1870.     var url = updateEl.getAttribute('url');
  1871.     var testURI = makeURI(url);
  1872.     if (testURI) {
  1873.       var interval = updateEl.getAttribute('interval');
  1874.       var info = {
  1875.         url: testURI.spec,
  1876.         interval: interval ? interval : DEFAULT_UPDATE_INTERVAL
  1877.       };
  1878.       return info;
  1879.     }
  1880.   }
  1881.  
  1882.   var url = DEFAULT_UPDATE_SERVER + this.mDetectFiles[aServiceName].leafName;
  1883.   var info = {
  1884.     url: makeURI(url).spec,
  1885.     interval: DEFAULT_UPDATE_INTERVAL
  1886.   };
  1887.   return info;
  1888. }
  1889.  
  1890. flockWebDetective.prototype.sendUpdateRequest =
  1891. function flockWebDetective_sendUpdateRequest(aServiceName, aListener)
  1892. {
  1893.   var updateInfo = this.getUpdateInfo(aServiceName);
  1894.  
  1895.   var hr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
  1896.     .createInstance(Ci.nsIXMLHttpRequest);
  1897.   hr.backgroundRequest = true;
  1898.   hr.open('GET', updateInfo.url);
  1899.  
  1900.   var lastModified = this.updateMetadataStore.getAttr(aServiceName,
  1901.                                                       'updatelastmodified');
  1902.   hr.setRequestHeader('If-Modified-Since', lastModified);
  1903.  
  1904.   var self = this;
  1905.   var updateInterval = updateInfo.interval;
  1906.  
  1907.   hr.onload = function(event) {
  1908.     var req = event.target;
  1909.     if (req.responseXML) {
  1910.       self.updateDetectFile(aServiceName, req.responseText, updateInterval);
  1911.       if (aListener) {
  1912.         aListener.onSuccess(null,aServiceName);
  1913.       }
  1914.     } else {
  1915.       if (aListener) {
  1916.         aListener.onError(null,aServiceName,null);
  1917.       }
  1918.     }
  1919.   }
  1920.  
  1921.   hr.send(null);
  1922. }
  1923.  
  1924. flockWebDetective.prototype.updateDetectFile =
  1925. function flockWebDetective_updateDetectFile(serviceName, contents, updateInterval)
  1926. {
  1927.   if (!contents) {
  1928.     this.updateMetadataStore.setAttr(serviceName, 'updateexpire',
  1929.                                      Date.now() + updateInterval * 86400000);
  1930.     return;
  1931.   }
  1932.  
  1933.   try {
  1934.     var detectFile = this.mDetectFiles[serviceName];
  1935.  
  1936.     var ostream = Cc['@mozilla.org/network/safe-file-output-stream;1']
  1937.       .createInstance(Ci.nsIFileOutputStream);
  1938.     ostream.init(detectFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0644, 0);
  1939.  
  1940.     var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter']
  1941.       .createInstance(Ci.nsIScriptableUnicodeConverter);
  1942.     converter.charset = 'UTF-8';
  1943.  
  1944.     var convdata = converter.ConvertFromUnicode(contents) + converter.Finish();
  1945.  
  1946.     ostream.write(convdata, convdata.length);
  1947.  
  1948.     if (ostream instanceof Ci.nsISafeOutputStream) {
  1949.       ostream.finish();
  1950.     } else {
  1951.       ostream.close();
  1952.     }
  1953.  
  1954.     this.loadDetectFile(detectFile);
  1955.  
  1956.     var updateInfo = this.getUpdateInfo(serviceName);
  1957.     this.updateMetadataStore.setAttr(serviceName, 'updateexpire',
  1958.                                      Date.now() + updateInfo.interval * 86400000);
  1959.  
  1960.     this.updateMetadataStore.setAttr(serviceName, 'updatelastmodified',
  1961.                                      (new Date()).toUTCString());
  1962.   }
  1963.   catch (e) { }
  1964. }
  1965. // END update service functions
  1966.  
  1967. // ========== END flockWebDetective class ==========
  1968.  
  1969.  
  1970.  
  1971. // =========================================
  1972. // ========== BEGIN XPCOM Support ==========
  1973. // =========================================
  1974.  
  1975. var Module = {
  1976.   _firstTime: true,
  1977.   registerSelf: function(aCompMgr, aFileSpec, aLocation, aType)
  1978.   {
  1979.     if (this._firstTime) {
  1980.       this._firstTime = false;
  1981.       throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  1982.     }
  1983.     aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1984.     aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType);
  1985.  
  1986.     var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  1987.                                     .getService(Components.interfaces.nsICategoryManager);
  1988.     categoryManager.addCategoryEntry("flock-startup", CLASS_NAME, "service," + CONTRACT_ID, true, true);
  1989.     categoryManager.addCategoryEntry("flockMigratable", CLASS_NAME, CONTRACT_ID, true, true);
  1990.   },
  1991.  
  1992.   unregisterSelf: function(aCompMgr, aLocation, aType)
  1993.   {
  1994.     aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1995.     aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);
  1996.   },
  1997.  
  1998.   getClassObject: function(aCompMgr, aCID, aIID)
  1999.   {
  2000.     if (!aIID.equals(Components.interfaces.nsIFactory)) {
  2001.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  2002.     }
  2003.     if (aCID.equals(CLASS_ID)) {
  2004.       return Factory;
  2005.     }
  2006.     throw Components.results.NS_ERROR_NO_INTERFACE;
  2007.   },
  2008.  
  2009.   canUnload: function(aCompMgr) { return true; }
  2010. };
  2011.  
  2012. var Factory = {
  2013.   createInstance: function(aOuter, aIID)
  2014.   {
  2015.     if (aOuter != null) {
  2016.       throw Components.results.NS_ERROR_NO_AGGREGATION;
  2017.     }
  2018.     return (new flockWebDetective()).QueryInterface(aIID);
  2019.   }
  2020. };
  2021.  
  2022. function NSGetModule(aCompMgr, aFileSpec)
  2023. {
  2024.   return Module;
  2025. }
  2026.  
  2027. // ========== END XPCOM Support ==========
  2028.